/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.blacksheep.jmoteextract.progress;

import java.io.IOException;
import java.util.HashMap;
import java.util.Observable;
import de.blacksheep.jmoteextract.remote.RemoteProgressObservable;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 */
public class ProgressManager extends Observable {
    
    private static ProgressManager instance;
    private static HashMap<Integer, ProgressObject> progressMap;
    
    private static final Object sync   = new Object();
    private static final Logger LOGGER = Logger.getLogger(ProgressManager.class.getName());

    private class ProgressObject {
        private RemoteProgressObservable ro = null;
        private long   bytesDone    = 0;
        private long   bytesTotal   = 0;
        private int    hashLastItem = 0;
        private String nameLastItem = null;
        private String errorMsg     = null;
        
        public void setRemoteProgressObservable(RemoteProgressObservable ro) {
            this.ro = ro;
        }
        
        public void setBytesDone(long bytes) {
            bytesDone = bytes;
        }
        
        public void setBytesTotal(long bytes) {
            bytesTotal = bytes;
        }
        
        public void setErrorMsg(String msg) {
            errorMsg = msg;
        }
        
        public void setLastItem(int hash, String name) {
            hashLastItem = hash;
            nameLastItem = name;
        }
        
        public RemoteProgressObservable getRemoteProgressObservable() {
            return ro;
        }
        
        public long getBytesDone() {
            return bytesDone;
        }
        
        public long getBytesTotal() {
            return bytesTotal;
        }
        
        public int getProgressInPercent() {
            float result = ((float) bytesDone / (float) bytesTotal);
            return (int) (result * 100);
        }
        
        public int getLastItemHash() {
            return hashLastItem;
        }
        
        public String getLastItemName() {
            return nameLastItem;
        }
        
        public String getLastError() {
            return errorMsg;
        }
    }
    
    private ProgressManager() {
        progressMap = new HashMap<Integer, ProgressObject>();        
    }
    
    public static ProgressManager getInstance() {
        if (instance == null) {
            instance = new ProgressManager();
            ProgressUpdateThread put = new ProgressUpdateThread();
            put.start();
        }
        return instance;
    }
    
    public boolean contains(int hash) {
        synchronized  (sync) {
            return progressMap.containsKey(hash);
        }
    }
    
    public void addLocalProgress(int hash, 
                                 RemoteProgressObservable ro) 
            throws IOException {
        
        LOGGER.log(Level.FINE, "Try to addManagedProgress for {0}...", hash);
        
        synchronized (sync) {
            if (!progressMap.containsKey(hash)) {
                LOGGER.log(Level.FINE, "Adding managed progress for {0}...", hash);

                ProgressObject po = new ProgressObject();
                po.setRemoteProgressObservable(ro);
                progressMap.put(hash, po);
                LOGGER.log(Level.FINE, "Added managed progress for {0}!", hash);
            }
        }
    }
    
    public void addNonLocalProgress(int hash) 
            throws IOException {
        
        LOGGER.log(Level.FINE, "Try to addObserverdProgress for {0}...", hash);
                
        synchronized (sync) {
            if (!progressMap.containsKey(hash)) {
                LOGGER.log(Level.FINE, "Adding observed progress for {0}...", hash);
                progressMap.put(hash, new ProgressObject());
                LOGGER.log(Level.FINE, "Added observed progress for {0}!", hash);
            }
        }
    }
    
    public void removeProgress(int hash) {
        LOGGER.log(Level.FINE, "Removing {0} from progressMap", hash);
        synchronized (sync) {
            progressMap.remove(hash);
        }
    }
    
    public void setFinished(int hash) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINE, "Try to setFinished for {0}...", hash);
        
        if (progress != null) {
            LOGGER.log(Level.FINE, "Setting {0} to finished", hash);
            setBytesDone(hash, progress.getBytesTotal());
        }
    }
    
    public boolean isFinished(int hash) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINE, "Try to prove isFinished for {0}...", hash);
        
        if (progress != null) {
            LOGGER.log(Level.FINE, "Checking {0} has finished", hash);
            return (progress.getBytesDone() == progress.getBytesTotal());
        }
        return true;
    }
    
    public void setBytesDone(int hash, long bytes) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINER, "Try to setBytesDone for {0}...", hash);
        
        if (progress != null && progress.getBytesDone() <= bytes) {
            LOGGER.log(Level.FINER, "Setting ''{0} Bytes done'' for {1}", new Object[]{bytes, hash});
            
            progress.setBytesDone(bytes);
            
            RemoteProgressObservable ro = progress.getRemoteProgressObservable();
            if (ro != null) {
                LOGGER.log(Level.FINER, "Notify remote observers");
                ro.notifySubscribers(
                        RemoteProgressObservable.REMOTE_SET_BYTESDONE, bytes);
            }
            
            LOGGER.log(Level.FINER, "Notify local observers");
            
            setChanged();
            notifyObservers(hash);
        }
    }
    
    public void setBytesTotal(int hash, long bytes) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINER, "Try to setBytesTotal for {0}...", hash);
        
        if (progress != null && progress.getBytesTotal() == 0) {
            LOGGER.log(Level.FINER, "Setting ''{0} Bytes in total'' for {1}", new Object[]{bytes, hash});
            
            progress.setBytesTotal(bytes);
            
            LOGGER.log(Level.FINER, "Notify local observers");

            setChanged();
            notifyObservers(hash);
            
            RemoteProgressObservable ro = progress.getRemoteProgressObservable();
            if (ro != null) {
                LOGGER.log(Level.FINER, "Notify remote observers");
                ro.notifySubscribers(
                        RemoteProgressObservable.REMOTE_SET_BYTESTOTAL, bytes);
            }
        }
    }
    
    public void setErrorMsg(int hash, String msg) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINER, "Try to setErrorMsg for {0}...", hash);
        
        if (progress != null) {
            LOGGER.log(Level.FINER, "Setting ''{0}'' as error message for {1}", new Object[]{msg, hash});
            
            progress.setErrorMsg(msg);
            
            LOGGER.log(Level.FINER, "Notify local observers");

            setChanged();
            notifyObservers(hash);
            
            RemoteProgressObservable ro = progress.getRemoteProgressObservable();
            if (ro != null) {
                LOGGER.log(Level.FINER, "Notify remote observers");
                ro.notifySubscribers(
                        RemoteProgressObservable.REMOTE_SET_ERRORMSG, msg);
            }
        }
    }

    public void setLastItem(int hash, int itemHash, String name) {
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        
        LOGGER.log(Level.FINER, "Try to setLastItem for {0}...", hash);
        
        if (progress != null) {
            LOGGER.log(Level.FINER, "Setting ''{0}: {1}'' as last processed item for {2}", new Object[]{itemHash, name, hash});
            
            progress.setLastItem(hash, name);

            // TODO: set last item
            //setChanged();
            //notifyObservers(hash);
        }
    }
    
    public long getBytesDone(int hash) {
        LOGGER.log(Level.FINER, "Try to getBytesDotal for {0}...", hash);        
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        return (progress == null) ? null : progress.getBytesDone();
    }
    
    public long getBytesTotal(int hash) {
        LOGGER.log(Level.FINER, "Try to getBytesTotal for {0}...", hash);
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        return (progress == null) ? null : progress.getBytesTotal();
    }
    
    public String getErrorMsg(int hash) {
        LOGGER.log(Level.FINER, "Try to getErrorMsg for {0}...", hash);
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        return (progress == null) ? null : progress.getLastError();
    }

    public String getLastItem(int hash) {
        LOGGER.log(Level.FINER, "Try to getLastItem for {0}...", hash);
        // TODO: get last item
        return "lastItem";
    }
    
    public int getProgressInPercent(int hash) {
        LOGGER.log(Level.FINER, "Try to getProgressInPercent for {0}...", hash);
        ProgressObject progress = null;
        synchronized (sync) {
            progress = progressMap.get(hash);
        }
        return (progress == null) ? null : progress.getProgressInPercent();
    }
}
